/*
 * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
 * All rights reserved.
 *
 * This file is part of Harmattan SmashMiner OpenGL game application.
 * 
 * Harmattan SmashMiner OpenGL Example Application version 1.0.0
 *  
 * Latest update: 15.4.2011
 *
 * The Harmattan SmashMiner OpenGL example application demonstrates how to use 
 * the OpenGL ES in Harmattan devices.
 *
 * This example is provided as a starting point for 3rd party
 * developers (you) to ease the implementation of OpenGL based 
 * games. Ideas, parts of code or methodologies that this
 * example application uses can be freely used without restrictions.
 *
 * See file README.txt how to build and compile this example application
 * in the Harmattan SDK environment. 
 * 
 * See file INSTALL.txt to find out what is required to run this
 * application and how to install the application to the device or
 * alternatively in QEMU-emulator.
 *
 * The LICENSE.txt file explains the license this software is provided
 * with. A copy of the same license is included in this source file.
 * 
 */

/*
 * Copyright (C) 2011 by Nokia Corporation.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/// \file gameobject.h

#ifndef GAMEOBJECT_H
#define GAMEOBJECT_H

#include "defines.h"     // include this before anything else.
#include "rotatingobject.h"

#include <QList>
#include <QGLWidget>    // just for GLfloat...

/*! \enum GameObjectType
  Type of the GameObject
*/
enum GameObjectType {
    /// Space craft a.k.a. Miner
    TypeSpaceCraft,
    /// Large asteroid
    TypeAsteroidLarge,
    /// Medium asteroid A
    TypeAsteroidMediumA,
    /// Medium asteroid B
    TypeAsteroidMediumB,
    /// Medium asteroid C
    TypeAsteroidMediumC,
    /// Enemy ufo
    TypeUfo,
    /// Enemy blackhole
    TypeBlackHole,
    /// Collectible mineral A
    TypeCollectibleMineralA,
    /// Collectible mineral B
    TypeCollectibleMineralB,
    /// Collectible mineral C
    TypeCollectibleMineralC,
    /// Collectible mineral D
    TypeCollectibleMineralD,
    /// Collectible bomb
    TypeCollectibleBomb,
    /// Collectible hyperjump
    TypeCollectibleHyperJump,
    /// Enemy missile
    TypeMissile,
    /// Enemy comet
    TypeComet,
    /// Enemy meteor shower
    TypeMeteorShower
};

/*! \enum GameObjectMass
  Mass of the GameObject
*/
enum GameObjectMass {
    /// No mass
    MassNone = 0,
    /// Tiny mass
    MassTiny = 1,
    /// Small mass
    MassSmall = 2,
    /// Medium mass
    MassMedium = 5,
    /// Large mass
    MassLarge = 10
};

/*! \enum ScoreValue
  Score value of the game object when collected or destroyed by player.
*/
enum ScoreValue {
    /// No score
    ScoreNone = 0,
    /// Large asteroid score
    ScoreAsteroidLarge = 10,
    /// Medium asteroid score
    ScoreAsteroidMedium = 15,
    /// Mineral A score
    ScoreMineralA = 25,
    /// Mineral B score
    ScoreMineralB = 50,
    /// Mineral C score
    ScoreMineralC = 75,
    /// Mineral D score
    ScoreMineralD = 100,
    /// Collectible bomb score
    ScoreBomb = 150,
    /// Collectible hyperjump score
    ScoreHyperJump = 160,
    /// Missile score when destroyed
    ScoreMissile = 170,
    /// Comet score when destroyed
    ScoreComet = 500,
    /// Blackhole score when destroyed
    ScoreBlackHole = 750,
    /// Ufo score when destroyed
    ScoreUfo = 1000,
    /// Meteror shower score when destroyed
    ScoreMeteorShower = 2000
};

/*! \enum SpeedLimit
  Maximum speed of the GameObject.
*/
enum SpeedLimit {
    /// Low speed
    SpeedLimitLow = 4,
    /// Medium speed
    SpeedLimitMedium = 10,
    /// High speed
    SpeedLimitHigh = 15,
    /// Very high speed
    SpeedLimitExtraHigh = 25
};

class GameObject
{
private:

    /*! \struct frameData
     * Structure for storing frame data for rewind mode
     * playback.
     */
    struct frameData {
        /// Frame id, the current rewind playback frame
        int frameId;
        /// Object x-coordinate in rewind playback frame frameId
        FPtype positionX;
        /// Object y-coordinate in rewind playback frame frameId
        FPtype positionY;
    };

public:

    GameObject(GameObjectType, int, FPtype, SpeedLimit, bool, 
               GameObjectMass mass = MassNone, ScoreValue scoreValue = ScoreNone);
    ~GameObject();

    // in MeteorShower::storeRadius() there is a need to update
    // GameObject::radius value which is a constant otherwise.

    friend class MeteorShower;

    /// Returns the x-coordinate of a game object
    inline FPtype posX() const { return positionX; }
    /// Returns the y-coordinate of a game object
    inline FPtype posY() const { return positionY; }

    /// Returns the x-axis velocity of a game object
    inline FPtype velX() const { return velocityX; }
    /// Returns the y-axis velocity of a game object
    inline FPtype velY() const { return velocityY; }

    /// Returns the game object type (one of \c ::GameObjectType values)
    inline GameObjectType objectType() const { return type; }
    /// Returns the radius of a game object
    inline FPtype objectRadius() const { return radius; }

    /// Returns the mass of a game object (one of ::GameObjectMass values)
    inline GameObjectMass mass() const { return objectMass; }
    /// Returns the score value of a game object
    inline int scoreValue() const { return score; }

    /// Sets the x-coordinate of a game object
    inline void setPosX(FPtype posX) { positionX = posX; }
    /// Sets the y-coordinate of a game object
    inline void setPosY(FPtype posY) { positionY = posY; }

    /// Sets the x-axis velocity of a game object
    inline void setVelX(FPtype velX) { velocityX = velX; }
    /// Sets the y-axis velocity of a game object
    inline void setVelY(FPtype velY) { velocityY = velY; }

    /// Sets the mass of a game object (one of ::GameObjectMass values)
    inline void setMass(GameObjectMass mass) { objectMass = mass; }

    /// Sets the score value of a game object
    inline void setScoreValue(ScoreValue scoreValue) { score = scoreValue; }

    /// Returns true if game object is in ''alive'' state.
    /// When game object is ''alive'' it's rendered in normal game play state
    inline bool isAlive() const { return (storedDiedFrame < 0); }

    /*! Sets game object to ''dead'' state.
     * When game object is in ''dead'' state it's not rendered in normal game 
     * play state but it may be rendered in rewind mode playback.
     *
     * \param frame Current game frame, the frame the game object died
     * \param time Current game time, the time the game object died
     */
    inline void setDead(int frame, FPtype time)
    {
        storedDiedFrame = frame;
        storedDiedTime = time;
    }

    /// Returns the game the game object died
    inline FPtype diedTime() const { return storedDiedTime; }
    /// Return the game frame the game object died
    inline int diedFrame() const { return storedDiedFrame; }

    void checkSpeed();          // scale the velocities according to speedlimit.
    void scaleSpeed(FPtype);    // scale the velocities according to a given speed.

#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

    bool rewindEnabled();
    void setRewindEnabled(bool rewindEnabled);

    bool isAliveAtFrame(int) const;

    void writePosition(int);

    void initRewindMode(int);
    void readPosition(int, GLfloat *);

#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

    static void setUnCollidable(QList<GameObject *> &);
    bool isCollidable(int currentFrame, GameObject *other) const;
    bool isCollidable(int currentFrame) const;

protected:
    /// Frame when GameObject was died. Drawn to screen at last time.
    int storedDiedFrame;
    /// Frame when GameObject was bord. Drawn to screen at firs time.
    int storedBornFrame;
    /// Time when GameObject was died. Drawn to screen at last time.
    FPtype storedDiedTime;

private:

    /*!
    Number of collision free frames.
    Assume that average framerate is around 50 fps -> time for a single frame is 1000 ms / 50 = 20 ms.
    Setting 5 collision free frames would make 0.1 seconds and it should be enough.
    NOTE! Bigger values are bad because normal collision logic WILL eventually be interfered.
    */
    static const int collisionFreeFrames = 5;
    /// Number of collision groups
    static int collisionGroupCounter;
    /// Type of GameObject
    GameObjectType type;
    /// Radius
    FPtype radius;
    /// Maximum speed of game object
    FPtype speedLimit;
    /// Current X position of game object on screen
    FPtype positionX;
    /// Current Y position of game object on screen
    FPtype positionY;
    /// Current velocity of game object on X axle
    FPtype velocityX;
    /// Current velocity of game object on Y axle
    FPtype velocityY;
    /// Mass of game object
    GameObjectMass objectMass;
    /// Score value
    ScoreValue score;
    /// Collision group ID for game object
    int collisionGroup;

#ifdef SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

private:
    /// Frame data for rewind mode
    QVector<frameData> * buffer;

protected:
    /// Current write index for frame data
    int writeIndex;
    /// Has game object wrapped
    bool hasWrapped;
    /// Current read index for frame data
    int readIndex;

#endif  // SMASHMINER_CONFIG_ENABLE_GAME_REWIND_MODE

};

/*! \def WRAPPING_FLAG_WRAP_X
  Wrapping X axle of the game object
*/
#define WRAPPING_FLAG_WRAP_X    1

/*! \def WRAPPING_FLAG_WRAP_Y
  Wrapping Y axle of the game object
*/
#define WRAPPING_FLAG_WRAP_Y    2

class WrappingGameObject : public GameObject
{
public:

    WrappingGameObject(GameObjectType, int, FPtype, SpeedLimit, bool,
                       GameObjectMass mass = MassTiny, ScoreValue scoreValue = ScoreNone);

    /// Getter for wrapping flags
    inline int wrappingFlags() { return storedWrappingFlags; }
    /// Getter for X axle wrap position
    inline FPtype wrappedPosX() { return wrappedPositionX; }
    /// Getter for Y axle wrap position
    inline FPtype wrappedPosY() { return wrappedPositionY; }
    /// Setter for X axle wrap position
    inline void setWrappedPosX(FPtype x) { wrappedPositionX = x; }
    /// Setter for Y axle wrap position
    inline void setWrappedPosY(FPtype y) { wrappedPositionY = y; }

    bool checkWrapping(FPtype, FPtype);
    void checkWrappingRewind(GLfloat *,FPtype, FPtype);

    /// Getter for wrapping disabled
    inline bool wrappingDisabled() { return wrappingDisabledFlag; }
    /// Setter for wrapping disabled
    inline void setWrappingDisabled(bool wrapping) { wrappingDisabledFlag = wrapping; }

private:
    /// Wrapping flags. \sa #WRAPPING_FLAG_WRAP_X \sa #WRAPPING_FLAG_WRAP_Y
    int storedWrappingFlags;
    /// Wrap position of the X axle
    FPtype wrappedPositionX;
    /// Wrap position of the Y axle
    FPtype wrappedPositionY;
    /// If wrapping is temporarily disabled
    bool wrappingDisabledFlag;
};

#endif // GAMEOBJECT_H
